package rpg.map;

import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.Timer;


import rpg.Rpg;
import rpg.action.Skill;
import rpg.ai.NpcAI;
import rpg.player.Player;
import rpg.script.CreatureScriptImpl;
import square.data.CreatureData;
import square.data.DataGroup;
import square.data.MapObjectData;
import square.data.SkillData;

/**
 * 
 * @author Pedro Silva Moreira
 */
public class Creature extends MapObject {

	private Long walkStart = 0L;
	private Timer walkTimer = null;

	private NpcAI intelligence;
	private CreatureScriptImpl script;

	private Integer attackDelay;
	private Long lastExhaustVerification = null;

	private Creature target;
	private ArrayList<Creature> targetOf = new ArrayList<Creature>();

	private Player player;

	// Freeze
	private Long freezeWalkStart = 0L, freezeWalkTime = 0L;
	private Long freezeTeleportStart = 0L, freezeTeleportTime = 0L;
	private Long freezeSkillStart = 0L, freezeSkillTime = 0L;

	// Creation
	public Creature(CreatureData dataReflect) {
		this.setDataReflect(dataReflect);

		if (this.getDataReflect() == null) {
			return;
		}

		CreatureData data = this.getDataReflect().instance();
		data.setOwner(this);
		this.setData(data);

		// Script
		if (data.getScript() != null) {
			CreatureScriptImpl script = (CreatureScriptImpl) data.getScript()
					.instance();
			script.setObject(this);
			this.setScript(script);
		}

		// Skill
		for (SkillData skill : this.getData().getSkills()) {
			skillOwn(skill);
		}
	}

	public Creature(String name) {
		this(DataGroup.findCreature(name));
	}

	// Damage Calculate
	public Integer[] damage(ArrayList<String> damages, int extra) {
		Integer result[] = { 0, 0 };

		if (damages.contains("physical"))
			result[0] += physicalDamage();
		else if (damages.contains("magic"))
			result[0] += magicDamage();

		result[0] += extra;

		// Critical
		int criticalRate = (this.getData().getCritical() + this.getData()
				.getLevel()) / 200;
		if (criticalRate > 25)
			criticalRate = 25;

		int random = new Random().nextInt(200);
		if (random <= criticalRate)
			result[1] = random;

		result[0] += result[1];

		return result;
	}

	private int physicalDamage() {
		return this.getData().getAttack();
	}

	private int magicDamage() {
		return this.getData().getAttack() * 1 / 2 + this.getData().getMagic();
	}

	// Resistance Calculate
	public int resistance(ArrayList<String> resistances) {
		int result = 0;

		if (resistances.contains("physical"))
			result += physicalResistance();
		else if (resistances.contains("magic"))
			result += magicResistence();

		return result;
	}

	private int physicalResistance() {
		return this.getData().getDefense();
	}

	private int magicResistence() {
		return this.getData().getDefense() * 1 / 2 + this.getData().getMagic();
	}

	// Skill Reaction
	public void changeHealth(int value, boolean critical) {
		// Effect
		Effect effect = new Effect("Text", 1500);
		String text = String.valueOf(Math.abs(value));

		if (!critical) {
			if (value > 0) {
				effect.setColor(Color.red);
			} else {
				effect.setColor(Color.blue);
			}
		} else {
			effect.setColor(Color.orange);
			text = "CRITICAL! " + text;
		}
		effect.getData().setName(text);
		this.getLocation().addEffect(effect);

		// Health
		this.getData().setHealth(this.getData().getHealth() - value);
		if (this.getData().getHealth() <= 0)
			this.onDie();
	}

	// Walk Event
	public boolean onWalk(Location next) {
		if (Rpg.disableEvents)
			return false;

		if (System.currentTimeMillis() - getFreezeWalkStart() < this
				.getFreezeWalkTime())
			return false;

		if (getScript() != null) {
			return getScript().onWalk(next);
		}
		return true;
	}

	public void onWalkFinish() {
		if (Rpg.disableEvents)
			return;

		if (getScript() != null) {
			getScript().onWalkFinish();
		}

		if (this.getPlayer() != null) {
			this.getPlayer().onCreatureWalkFinish();
		}
	}

	// Teleport Event
	public boolean onTeleport(Location next) {
		if (Rpg.disableEvents)
			return false;

		if (System.currentTimeMillis() - getFreezeTeleportStart() < this
				.getFreezeTeleportTime())
			return false;

		if (getScript() != null) {
			return getScript().onTeleport(next);
		}
		return true;
	}

	public void onTeleportFinish() {
		if (Rpg.disableEvents)
			return;

		if (getScript() != null) {
			getScript().onTeleportFinish();
		}
	}

	// Target Event
	public boolean onTarget(Creature creature) {
		if (Rpg.disableEvents)
			return false;

		if (this.getTarget() != null) {
			if (!this.getTarget().onTargetOfLose(this))
				return false;
		}

		if (creature != null && !creature.onTargetOf(this)) {
			return false;
		}

		if (getScript() != null) {
			if (!getScript().onTarget(creature))
				return false;
		}
		this.setTarget(creature);
		return true;
	}

	private boolean onTargetOf(Creature creature) {
		if (this.getScript() != null && !this.getScript().onTargetOf(creature))
			return false;

		this.getTargetOf().add(creature);
		return true;
	}

	private boolean onTargetOfLose(Creature creature) {
		if (creature.getLocation() == null)
			return true;

		if (creature.getScript() == null
				|| creature.getScript().onTargetOfLose(creature)) {
			this.getTargetOf().remove(creature);
			return true;
		}
		return false;
	}

	// Live Events
	public boolean onDie() {
		if (this.getScript() != null) {
			if (!this.getScript().onDie())
				return false;
		}

		if (this.getPlayer() != null) {
			this.getPlayer().onDie();
		}
		this.removeFromMap();

		onTarget(null);
		for (Creature it : this.getTargetOf()) {
			it.onTarget(null);
		}

		return true;
	}

	// Skill Events
	public boolean onSkill(Skill skill, MapObject mapObject, boolean exhaust) {
		if (System.currentTimeMillis() - getFreezeSkillStart() < this
				.getFreezeSkillTime())
			return false;

		if (this.getData().getExhaust() > 0) {
			int remove = (int) (this.getLastExhaustVerification() / this
					.getData().getExhaustTime());
			this.getData().setExhaust(this.getData().getExhaust() - remove);
		}

		if (this.getData().getExhaust() <= 0) {
			this.setLastExhaustVerification(System.currentTimeMillis());
			this.getData().setExhaust(0);
		} else if (exhausted()) {
			return false;
		}

		boolean result = skill.onUse(mapObject);
		if (result) {
			this.getData().setExhaust(
					this.getData().getExhaust() + skill.getData().getExhaust());
		}
		return result;
	}

	// Skill Control
	private boolean exhausted() {
		return this.getData().getExhaust() > this.getData().getMaxExhaust();
	}

	private void skillOwn(SkillData data) {
		data.setOwner(new Skill(this, data));
	}

	public void addSkill(SkillData skill) {
		for (SkillData it : this.getData().getSkills()) {
			if (it.getName().equals(skill.getName()))
				return;
		}

		SkillData instance = skill.instance();
		skillOwn(instance);

		this.getData().getSkills().add(instance);
	}

	public void removeSkill(Skill skill) {
		this.getData().getSkills().remove(skill.getData());
	}

	public ArrayList<Skill> avaibleSkills() {
		ArrayList<Skill> result = new ArrayList<Skill>();

		for (SkillData it : this.getData().getSkills()) {
			Skill skill = (Skill) it.getOwner();
			if (skill.available())
				result.add(skill);
		}

		return result;
	}

	public boolean randomSkill(MapObject mapObject, boolean exhaust) {
		ArrayList<Skill> skills = avaibleSkills();
		Random random = new Random();
		while (skills.size() > 0) {
			Skill skill = skills.get(random.nextInt(skills.size()));
			if (onSkill(skill, mapObject, true))
				return true;
			else
				skills.remove(skill);
		}
		return false;
	}

	// Map Control
	@Override
	public void removeFromMap() {
		this.getLocation().setCreature(null);
	}

	// Walk
	private boolean walkAccept() {
		if (this.getData().getSpeed() == 0 || walkPercent() > 0
				|| (walkTimer != null && walkTimer.isRunning())) {
			return false;
		}

		Location next = walkDestiny();
		if (next == null || !next.walkAble()) {
			return false;
		}

		return true;
	}

	private Location walkDestiny() {
		Map map = this.getMap();
		if (map == null) {
			return null;
		}

		int x = this.getLocation().getX();
		int y = this.getLocation().getY();
		int z = this.getLocation().getZ();

		switch (this.getSide()) {
		case CreatureData.SIDE_LEFT:
			if (x == 0) {
				return null;
			}
			x--;
			break;
		case CreatureData.SIDE_RIGHT:
			if (x == map.getWidth() - 1) {
				return null;
			}
			x++;
			break;
		case CreatureData.SIDE_UP:
			if (y == 0) {
				return null;
			}
			y--;
			break;
		case CreatureData.SIDE_DOWN:
			if (y == map.getHeight() - 1) {
				return null;
			}
			y++;
			break;
		case CreatureData.SIDE_LEFT_UP:
			if (x == 0 || y == 0) {
				return null;
			}
			x--;
			y--;
			break;
		case CreatureData.SIDE_RIGHT_UP:
			if (y == 0 || x == map.getWidth() - 1) {
				return null;
			}
			x++;
			y--;
			break;
		case CreatureData.SIDE_LEFT_DOWN:
			if (x == 0 || y == map.getHeight() - 1) {
				return null;
			}
			x--;
			y++;
			break;
		case CreatureData.SIDE_RIGHT_DOWN:
			if (x == map.getWidth() - 1 || y == map.getHeight() - 1) {
				return null;
			}
			x++;
			y++;
			break;
		}
		return (Location) map.getLocation(x, y, z);
	}

	private Integer walkTime() {
		Integer result = 50000 / this.getData().getSpeed();
		if (this.getSide() > 3)
			result = (int) (result * 1.4);
		return result;
	}

	public Integer walkPercent() {
		if (getWalkStart() == 0) {
			return 0;
		}

		Long dif = System.currentTimeMillis() - getWalkStart();
		Integer percent = (int) (dif * 100 / walkTime());

		if (percent >= 100) {
			return 100;
		}
		return percent;
	}

	public int walkState() {
		Integer percent = walkPercent();
		if (percent < 10) {
			return CreatureData.STATE_STOP;
		}
		if (percent < 50) {
			return CreatureData.STATE_WALK_START;
		}
		if (percent < 90) {
			return CreatureData.STATE_WALK_END;
		}
		return CreatureData.STATE_STOP;
	}

	public boolean walk() {
		Location next = walkDestiny();
		if (!walkAccept() || !onWalk(next)) {
			return false;
		}
		walkStart = System.currentTimeMillis();
		if (walkTimer == null) {
			next.setMovingCreature(this);

			final Creature mapCreature = this;
			walkTimer = new Timer(walkTime(), new ActionListener() {

				@Override
				public void actionPerformed(ActionEvent e) {
					Location next = walkDestiny();
					next.setMovingCreature(null);

					walkTimer.stop();
					walkTimer = null;
					setWalkStart(0L);

					if (next != null && next.walkAble()) {
						next.setCreature(mapCreature);
						onWalkFinish();
					}
				}
			});
		} else {
			walkTimer.setDelay(walkTime());
		}
		walkTimer.start();
		return true;
	}

	public boolean walkTo(Location location, boolean escape) {
		if (this.getWalkStart() != 0L) {
			return false;
		}

		ArrayList<Location> next = getLocation().pathTo(location, true, true,
				escape);
		if (next.isEmpty()) {
			return false;
		}

		Integer fromX = this.getLocation().getX();
		Integer fromY = this.getLocation().getY();

		Integer toX = next.get(0).getX();
		Integer toY = next.get(0).getY();

		Boolean right = fromX < toX;
		Boolean left = fromX > toX;
		Boolean up = fromY > toY;
		Boolean down = fromY < toY;

		if (right) {
			if (up)
				setSide(MapObjectData.SIDE_RIGHT_UP);
			else if (down)
				setSide(MapObjectData.SIDE_RIGHT_DOWN);
			else
				setSide(MapObjectData.SIDE_RIGHT);
		} else if (left) {
			if (up)
				setSide(MapObjectData.SIDE_LEFT_UP);
			else if (down)
				setSide(MapObjectData.SIDE_LEFT_DOWN);
			else
				setSide(MapObjectData.SIDE_LEFT);
		} else if (up) {
			setSide(MapObjectData.SIDE_UP);
		} else {
			setSide(MapObjectData.SIDE_DOWN);
		}

		return walk();
	}

	// Teleport
	public boolean teleport(Location location) {
		if (!location.walkAble()) {
			return false;
		}

		if (walkTimer != null && walkTimer.isRunning()) {
			walkTimer.stop();
			setWalkStart(0L);
		}

		if (!onTeleport(location)) {
			return false;
		}
		location.setCreature(this);
		onTeleportFinish();
		return true;
	}

	// Get & Set
	@Override
	public CreatureData getData() {
		return (CreatureData) super.getData();
	}

	@Override
	public CreatureData getDataReflect() {
		return (CreatureData) super.getDataReflect();
	}

	public Long getWalkStart() {
		return walkStart;
	}

	public void setWalkStart(Long walkStart) {
		this.walkStart = walkStart;
	}

	public Timer getWalkTimer() {
		return walkTimer;
	}

	public void setWalkTimer(Timer walkTimer) {
		this.walkTimer = walkTimer;
	}

	public NpcAI getIntelligence() {
		return intelligence;
	}

	public void setIntelligence(NpcAI intelligence) {
		this.intelligence = intelligence;
	}

	public CreatureScriptImpl getScript() {
		return script;
	}

	public void setScript(CreatureScriptImpl script) {
		this.script = script;
	}

	public Creature getTarget() {
		return target;
	}

	private void setTarget(Creature target) {
		this.target = target;
	}

	public Integer getAttackDelay() {
		return attackDelay;
	}

	public void setAttackDelay(Integer attackDelay) {
		this.attackDelay = attackDelay;
	}

	public Player getPlayer() {
		return player;
	}

	public void setPlayer(Player player) {
		this.player = player;
	}

	public Long getLastExhaustVerification() {
		return lastExhaustVerification;
	}

	public void setLastExhaustVerification(Long lastExhaustVerification) {
		this.lastExhaustVerification = lastExhaustVerification;
	}

	public Long getFreezeWalkStart() {
		return freezeWalkStart;
	}

	public void setFreezeWalkStart(Long freezeWalkStart) {
		this.freezeWalkStart = freezeWalkStart;
	}

	public Long getFreezeWalkTime() {
		return freezeWalkTime;
	}

	public void setFreezeWalkTime(Long freezeWalkTime) {
		this.freezeWalkTime = freezeWalkTime;
	}

	public Long getFreezeTeleportStart() {
		return freezeTeleportStart;
	}

	public void setFreezeTeleportStart(Long freezeTeleportStart) {
		this.freezeTeleportStart = freezeTeleportStart;
	}

	public Long getFreezeTeleportTime() {
		return freezeTeleportTime;
	}

	public void setFreezeTeleportTime(Long freezeTeleportTime) {
		this.freezeTeleportTime = freezeTeleportTime;
	}

	public Long getFreezeSkillTime() {
		return freezeSkillTime;
	}

	public void setFreezeSkillTime(Long freezeSkillTime) {
		this.freezeSkillTime = freezeSkillTime;
	}

	public Long getFreezeSkillStart() {
		return freezeSkillStart;
	}

	public void setFreezeSkillStart(Long freezeSkillStart) {
		this.freezeSkillStart = freezeSkillStart;
	}

	public ArrayList<Creature> getTargetOf() {
		return targetOf;
	}

	public void setTargetOf(ArrayList<Creature> targetOf) {
		this.targetOf = targetOf;
	}
}